from __future__ import with_statement
#!/usr/bin/python2.5
#
# Copyright 2008 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Appspotimage, a simple web service running on App Engine.

Specific App Engine features demonstrated include:
 * the Image API, which is used for providing the web service functionality
 * the Datastore API, which is used to store the images uploaded
"""

__author__ = 'Carlos Merida-Campos'

import cgi
import os
import logging
import ThreeScale
import contextlib
from xml.dom import minidom
from xml.dom.minidom import Document
import exceptions
import imghdr


from google.appengine.api import images
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template

import wsgiref.handlers

THREE_SCALE_BACKEND = "http://www.appspotimage.com"
THREE_SCALE_PROVIDER_KEY = "your_provider_key. go to www.3scalesolutions.com to get one"
#SERVICEDOMAIN = 'http://localhost:8084'
SERVICEDOMAIN = 'http://imagetweaker.appspot.com'

TEXT_CONTENT_TYPE = "text/plain"
CONTENT_TYPE_HEADER = "Content-Type"
CONTENT_TYPE_PNG = "image/png"
CONTENT_TYPE_JPEG = "image/jpeg"
XML_CONTENT_TYPE = "application/xml"
XML_ENCODING = "utf-8"

class Error(StandardError):
 pass
class ImageNotFound(Error): #Exception raised when no image is found
 pass

logging.getLogger().setLevel(logging.DEBUG)

interface = ThreeScale.Interface(THREE_SCALE_BACKEND, THREE_SCALE_PROVIDER_KEY)

@contextlib.contextmanager
def imageExcpHandler(ctx, *transaction_id):
  try:
    yield {}
  except (ThreeScale.UserKeyInvalid, ThreeScale.ContractNotActive, ThreeScale.ProviderKeyInvalid), exc:
    xml_error_response(ctx, 403,'appspotimage.invalid_key', 'authentication key is not valid: '+exc.message )    
  except (ThreeScale.LimitsExceeded), exc:
    xml_error_response(ctx, 403 ,'appspotimage.usage_exceeded', 'The usage of the API has reached the permitted limit: '+exc.message )
  except (images.LargeImageError, images.BadImageError, images.TransformationError), exc:
    xml_error_response(ctx, 400 ,'appspotimage.invalid_image', 'The image provided is too big or corrupt: '+exc.message )    
  except (ImageNotFound), exc:
    xml_error_response(ctx, 404 ,'appspotimage.not_found', 'The image requested has not been found: '+exc.message )
  except (images.NotImageError), exc:
    xml_error_response(ctx, 400 ,'appspotimage.invalid_encoding', 'The indicated encoding is not supported, valid encodings are PNG and JPEG: '+exc.message )    
  except (ValueError, images.BadRequestError), exc:
    xml_error_response(ctx, 400 ,'appspotimage.invalid_parameters', 'The indicated parameters to manipulate the image are not valid: ' +exc.message)
  except (ThreeScale.TransactionNotFound, ThreeScale.MetricInvalid, Exception), exc:
    xml_error_response(ctx, 500 ,'system.other', 'An unexpected error in the web service has happened: ' +exc.message)
  finally:
   try: 
    for trid in transaction_id: interface.confirm(trid,hit=1) #If there was an operation started, we roll it back
   except (ThreeScale.TransactionNotFound, Exception), exc:
     xml_error_response(ctx, 500 ,'system.other', 'An unexpected error in the web service has happened: ' +exc.message)
       

def xml_error_response(ctx, status, error_id, error_msg):
    ctx.error(status)
    doc = Document()
    errorcard = doc.createElement("error")
    errorcard.setAttribute("id", error_id)
    doc.appendChild(errorcard)
    ptext = doc.createTextNode(error_msg)
    errorcard.appendChild(ptext)    
    ctx.response.headers[CONTENT_TYPE_HEADER] = XML_CONTENT_TYPE
    ctx.response.out.write(doc.toxml(XML_ENCODING))


class Picture(db.Model):
  """Storage for a picture and its associated metadata.

  Properties:
    key: Identification key of the person who submitted the picture
    picid: user entered identificator for the picture
    data: data for the original picture
    thumbnail_data: png format data for the thumbnail for this picture
  """
  threescalekey = db.StringProperty()
  picid = db.StringProperty()
  data = db.BlobProperty()
  thumbnail_data = db.BlobProperty()




class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    self.response.out.write("""
     <form action="/archive" enctype="multipart/form-data" method="post">
      <div><label>Image Identifier:</label></div>
      <div><input type="text" name="img_id"/></div>
      <div><label>3scale Key:</label></div>
      <div><input type="text" name="key"/></div>
      <div><label>Image:</label></div>
      <div><input type="file" name="picfile"/></div>
      <div><input type="submit" value="Post Image"></div>
     </form> 
     <p>testKey: 3scale-d54a9fc683b4478db987af955766bd7c</p>
     </body>
     </html>""")


class UpDownload(webapp.RequestHandler):
 def get(self):
   """Downloads an image.
      Args:
         key: private integrator key
         img_id: user defined identifier to address the specific image
   """
   threescalekey = cgi.escape(self.request.get('key'))
   with imageExcpHandler(self):
     transaction = interface.start(threescalekey, hit = 1) #starting 3scale transaction
     with imageExcpHandler(self,transaction['id']): 
       img_id = cgi.escape(self.request.get('img_id'))
       query = Picture.all()
       query.filter('threescalekey =', threescalekey)
       query.filter('picid = ', img_id)
       pictures = query.fetch(limit=1)
       if len(pictures) == 0:      
         raise ImageNotFound("No existing image with such 3scale key and picture id")
       else:
         image= pictures[0]
         transaction = interface.confirm(transaction['id'], hit=1, transfer_out=(len(image.data))/1000) #Informing 3scale on the transaction result
         imgtype=imghdr.what('filename',image.data)
         if imgtype == 'png' :
           self.response.headers[CONTENT_TYPE_HEADER] = CONTENT_TYPE_PNG
         elif imgtype == 'jpeg':
           self.response.headers[CONTENT_TYPE_HEADER] = CONTENT_TYPE_JPEG
         self.response.out.write(image.data)
  
    

 def post(self):
      """Uploads an image to be processed.
        Returns the url of the thumbnail
        Args:
            key: private integrator key
            picfile: the image that it is being uploaded
            img_id: user defined identifier to address the specific image
      """
      threescale_key = cgi.escape(self.request.get('key'))
      img_id = cgi.escape(self.request.get('img_id'))
      img_data = self.request.POST.get('picfile').file.read()
   
      # with imageExcpHandler(self):
      transaction = interface.start(threescale_key, hit = 1) #Validating the transaction with 3scale
      #  with imageExcpHandler(self,transaction['id']): 
      img = images.Image(img_data)
      #imgtype=getImageInfo(img)
      imgtype=imghdr.what('filename',img_data)
      if imgtype != 'png' and imgtype != 'jpeg':
         raise images.NotImageError("Unknown image type")
      img.resize(60, 100)
      thumbnail_data = img.execute_transforms(images.PNG)
      imagesize=len(img_data)
      storageoccupied=((imagesize+len(thumbnail_data)))
  
      query = Picture.all()
      query.filter('threescalekey =', threescale_key)
      query.filter('picid = ', img_id)
      pictures= query.fetch(limit=1)
      if len(pictures) == 0:
         reference=Picture(  threescalekey=threescale_key,
                   picid=img_id,
                   data=img_data,
                   thumbnail_data=thumbnail_data).put()
      else:
         existing=pictures[0]
         storageoccupied=storageoccupied-len(existing.data)-len(existing.thumbnail_data)  
         existing.data=img_data
         existing.thumbnail_data=thumbnail_data
         reference=db.put(existing)
  
      interface.confirm(transaction['id'], hit=1, storage=storageoccupied/1000, transfer_in=imagesize/1000)
  
      self.response.headers[CONTENT_TYPE_HEADER] = TEXT_CONTENT_TYPE
      self.response.out.write(SERVICEDOMAIN+'/image/'+str(reference))
  
class Manipulate(webapp.RequestHandler):
 def get(self, display_type):
   """ Identifies the type of operation to be done, and pass it to the appropriate function
   """
   threescalekey = cgi.escape(self.request.get('key'))
   img_id = cgi.escape(self.request.get('img_id'))
   new_img_id = cgi.escape(self.request.get('new_img_id'))
   output_encoding = cgi.escape(self.request.get('output_encoding'))

   with imageExcpHandler(self):
    transaction = interface.start(threescalekey, hit = 1) #Validating the user and its usage
    with imageExcpHandler(self,transaction['id']):     
      if output_encoding == 'PNG':
        output_encoding=images.PNG
      elif output_encoding == 'JPEG':
        output_encoding=images.JPEG
      else:
        raise images.NotImageError()
   
      query = Picture.all()
      query.filter('threescalekey =', threescalekey)
      query.filter('picid = ', img_id)
      pictures= query.fetch(limit=1)
      if len(pictures) == 0:
        raise ImageNotFound("No existing image with such 3scale key and picture id") 
      picture=pictures[0]
      transactionid=transaction['id']  
  
      if 'resize' == cgi.escape(display_type.lower()):
        transformed=self.resize(picture, output_encoding)
      elif 'crop' == cgi.escape(display_type.lower()):
        transformed=self.crop(picture, output_encoding)
      elif 'rotate' == cgi.escape(display_type.lower()):
        transformed=self.rotate(picture, output_encoding)
      elif 'horizontal_flip'  == cgi.escape(display_type.lower()):
        transformed=self.horizontal_flip(picture, output_encoding)
      elif 'vertical_flip' == cgi.escape(display_type.lower()):
        transformed=self.vertical_flip(picture, output_encoding)
      elif 'im_feeling_lucky' == cgi.escape(display_type.lower()):
        transformed=self.im_feeling_lucky(picture, output_encoding)
   
      img2 = images.Image(transformed)
      img2.resize(60, 100)
      thumbnail_data = img2.execute_transforms(images.PNG)
  
      #Searching for the image (if the image identified by the provided name existed, we will replace it)
      query = Picture.all()
      query.filter('threescalekey =', threescalekey)
      query.filter('picid = ', new_img_id)
      pictures= query.fetch(limit=1)
      sizeocupied=0
      #new size to inform threescale
      sizeocupied=len(transformed)+len(thumbnail_data)
      if len(pictures) == 0:
        resultado=Picture(  threescalekey=picture.threescalekey,
                picid=new_img_id,
                data=transformed,
                thumbnail_data=thumbnail_data).put()
      else:
        newpicture=pictures[0]
        #previous size
        sizeocupied=sizeocupied-len(newpicture.data)-len(newpicture.thumbnail_data)
        newpicture.data=transformed
        newpicture.thumbnail_data=thumbnail_data
        resultado=db.put(newpicture)
  
      #We confirm the operation with 3scale
      interface.confirm(transactionid, hit = 1, storage = sizeocupied/1000)
      self.response.out.write(SERVICEDOMAIN+'/image/'+str(resultado))
  

 def resize(self, picture, output_encoding):
    """Modifies an image and gets a url of the result.
      Args:
         key: private integrator key
         img_id: user defined identifier to address the specific image
         new_img_id: user defined identifier to assign to the modified image
         width: width to resize the image
         height: height to resize the image
         output_encoding: output encoding [PNG, JPEG] 
    """
    width = cgi.escape(self.request.get('width'))
    height = cgi.escape(self.request.get('height'))

    img = images.Image(picture.data)
    img.resize(width=int(width), height=int(height))
    transformed = img.execute_transforms(output_encoding)
    return transformed
    
 def crop(self, picture, output_encoding):
   """Modifies an image and gets a url of the result.
      Args:
         left_x: The left border of the bounding box, as a proportion of the image width specified as a float value from 0.0 to 1.0 (inclusive).
         topy_y: The top border of the bounding box, as a proportion of the image height specified as a float value from 0.0 to 1.0 (inclusive).
         right_x: The right border of the bounding box, as a proportion of the image width specified as a float value from 0.0 to 1.0 (inclusive).
         bottom_y: The bottom border of the bounding box, as a proportion of the image height specified as a float value from 0.0 to 1.0 (inclusive).
         output_encoding: output encoding [PNG, JPEG] 
   """
   left_x = float(cgi.escape(self.request.get('left_x')))
   topy_y = float(cgi.escape(self.request.get('topy_y')))
   right_x = float(cgi.escape(self.request.get('right_x')))
   bottom_y = float(cgi.escape(self.request.get('bottom_y')))

   img = images.Image(picture.data)
   img.crop(left_x, topy_y, right_x, bottom_y)
   transformed = img.execute_transforms(output_encoding)
   return transformed


 def rotate(self, picture, output_encoding):
   """Modifies an image and gets a url of the result.
      Args:
         degree: The rotation degree, in multiples of 90.
         output_encoding: output encoding [PNG, JPEG] 
   """
   degree = cgi.escape(self.request.get('degree'))

   img = images.Image(picture.data)
   img.rotate(int(degree))
   transformed = img.execute_transforms(output_encoding)
   return transformed

 def horizontal_flip(self, picture, output_encoding):
   """Modifies an image and gets a url of the result.
      Flips an image horizontally. The edge that was the left becomes the right edge, and vice versa
      Args:
         output_encoding: output encoding [PNG, JPEG] 
   """
   img = images.Image(picture.data)
   img.horizontal_flip()
   transformed = img.execute_transforms(output_encoding)
   return transformed

 def vertical_flip(self, picture, output_encoding):
   """Modifies an image and gets a url of the result.
      Flips an image vertically. The edge that was the top becomes the bottom edge, and vice versa.
      Args:
         output_encoding: output encoding [PNG, JPEG] 
   """
   img = images.Image(picture.data)
   img.vertical_flip()
   transformed = img.execute_transforms(output_encoding)
   return transformed

 def im_feeling_lucky(self, picture, output_encoding):
   """Modifies an image and gets a url of the result.
      Adjusts the contrast and color levels of an image according to an algorithm for improving photographs. This is similar to the "I'm Feeling Lucky" feature of Google Picasa. The method returns the transformed image in the same format.
      Args:
         output_encoding: output encoding [PNG, JPEG] 
   """
   img = images.Image(picture.data)
   img.im_feeling_lucky()
   transformed = img.execute_transforms(output_encoding)
   return transformed


class ServeImage(webapp.RequestHandler):
  """Handler for dynamically serving an image from the datastore.
     Very simple - it just pulls the appropriate data out of the datastore
     and serves it.
  """
  def get(self, display_type, pic_key):
    """Dynamically serves a PNG image from the datastore.
     Args:
      type: a string describing the type of image to serve (image or thumbnail)
      pic_key: the key for a Picture model that holds the image
    """
    with imageExcpHandler(self):
      image = db.get(pic_key)
      threescalekey=image.threescalekey
      picid=image.picid
      transaction = interface.start(threescalekey, hit = 1) #Reporting usage
      with imageExcpHandler(self,transaction['id']): 
        transfered=0    
        if display_type == 'image':
          img_type=imghdr.what('filename',image.data)
          if(img_type == 'png'):
             self.response.headers[CONTENT_TYPE_HEADER] = CONTENT_TYPE_PNG
          else:
             self.response.headers[CONTENT_TYPE_HEADER] = CONTENT_TYPE_JPEG
          interface.confirm(transaction['id'], hit=1, transfer_out=len(image.data)/1000) #Confirming transaction
          self.response.out.write(image.data)
        elif display_type == 'thumbnail':
          interface.confirm(transaction['id'], hit=1, transfer_out=len(image.thumbnail_data)/1000) #Confirming transaction
          self.response.headers[CONTENT_TYPE_HEADER] = CONTENT_TYPE_PNG
          self.response.out.write(image.thumbnail_data)

        

application = webapp.WSGIApplication(
                                  [('/archive', UpDownload),
                                   ('/(resize|crop|rotate|horizontal_flip|vertical_flip|im_feeling_lucky)', Manipulate),
                                   ('/(thumbnail|image)/([-\w]+)', ServeImage),
                                   ('/upload', MainPage)
                                  ],
                                  debug=True)

def main():
 run_wsgi_app(application)

if __name__ == '__main__':
 main()



